﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using AndersLiu.Reflector.Properties;
using AndersLiu.Reflector.Program.UI.AssemblyTreeNode;
using AndersLiu.Reflector.Core;
using Microsoft.Cci;

namespace AndersLiu.Reflector.Program.UI
{
    partial class MainWindow : Form
    {
        public MainWindow()
            : this(null)
        {
        }

        public MainWindow(string pendingWorkspaceFile)
        {
            InitializeComponent();

            _pendingWorkspaceFile = pendingWorkspaceFile;

            trvWorkspace.ImageList = NodeImages.CreateImageList();

            _history.Changed += history_Changed;
        }

        /// <summary>
        /// Save a workspace file path that should be open when window loaded.
        /// </summary>
        private string _pendingWorkspaceFile;

        /// <summary>
        /// Save the workspace currently loaded.
        /// </summary>
        private Workspace _workspace;

        /// <summary>
        /// Save a tree node builder used to build tree nodes.
        /// </summary>
        private TreeNodeBuilder _treeNodeBuilder = new TreeNodeBuilder();

        private HistoryList<TreeNode> _history = new HistoryList<TreeNode>();

        /// <summary>
        /// Mapping between tree nodes and units.
        /// </summary>
        private Dictionary<TreeNode, IUnit> _treeNodeUnitMap = new Dictionary<TreeNode, IUnit>();

        private void MainWindow_Load(object sender, EventArgs e)
        {
            this.Icon = Resources.ReflectorIcon;

            OpenWorkspaceFile(_pendingWorkspaceFile);
            UpdateUIBaseOnHistory();
        }

        private void MainWindow_DragEnter(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
                return;

            e.Effect = DragDropEffects.Copy;
        }

        private void MainWindow_DragDrop(object sender, DragEventArgs e)
        {
            if (!e.Data.GetDataPresent(DataFormats.FileDrop))
                return;

            var filenames = (string[])e.Data.GetData(DataFormats.FileDrop);

            // Tread all files as assembly.
            foreach (var fn in filenames)
                OpenUnit(fn);
        }

        private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!SafeCloseWorkspace())
                e.Cancel = true;
        }

        private void mnuFileAddAssembly_Click(object sender, EventArgs e)
        {
            using (var dlg = new OpenFileDialog())
            {
                dlg.Filter = ".NET Assembly (*.exe;*.dll)|*.exe;*.dll|.NET Module (*.netmodule)|*.netmodule|All Files (*.*)|*.*";
                dlg.Multiselect = true;
                if (DialogResult.OK != dlg.ShowDialog())
                    return;

                foreach (var fn in dlg.FileNames)
                    OpenUnit(fn);
            }
        }

        private void mnuFileRemoveAssembly_Click(object sender, EventArgs e)
        {
            var unit = GetSelectedWorkspaceUnit();
            _workspace.RemoveUnit(unit);
        }

        private void mnuFileNewWorkspace_Click(object sender, EventArgs e)
        {
            if (!SafeCloseWorkspace())
                return;

            OpenWorkspaceFile(null);
        }

        private void mnuFileOpenWorkspace_Click(object sender, EventArgs e)
        {
            if (!SafeCloseWorkspace())
                return;

            using (var dlg = new OpenFileDialog())
            {
                dlg.Filter = "Reflector .NET Workspace (*.reflector)|*.reflector|All Files (*.*)|*.*";
                if (DialogResult.OK != dlg.ShowDialog())
                    return;

                OpenWorkspaceFile(dlg.FileName);
            }
        }

        private void mnuFileSaveWorkspace_Click(object sender, EventArgs e)
        {
            SaveWorkspace();
        }

        private void mnuFileExit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void mnuViewBack_Click(object sender, EventArgs e)
        {
            if (_history.CanGoBack)
                _history.GoBack(menuStrip);
        }

        private void mnuViewForward_Click(object sender, EventArgs e)
        {
            if (_history.CanGoForward)
                _history.GoForward(menuStrip);
        }

        private void mnuViewRefresh_Click(object sender, EventArgs e)
        {
            LoadWorkspaceUnits();
        }

        private void mnuHelpSendFeedBack_Click(object sender, EventArgs e)
        {
            Process.Start("http://reflector.codeplex.com/Thread/List.aspx");
        }

        private void mnuHelpCheckNewVersion_Click(object sender, EventArgs e)
        {
            MessageBox.Show("mnuHelpCheckNewVersion_Click");
        }

        private void mnuHelpAbout_Click(object sender, EventArgs e)
        {
            using (var dlg = new AboutDialog())
            {
                dlg.ShowDialog();
            }
        }

        private void btnViewBack_DropDownOpening(object sender, EventArgs e)
        {
            if (btnViewBack.Enabled)
            {
                btnViewBack.DropDown.Items.Clear();

                var i = 1;
                foreach (var s in _history.BackSteps)
                {
                    var item = new ToolStripMenuItem((s.Tag as IAssemblyTreeNode<TreeNode>).Title)
                    {
                        Tag = i,
                    };

                    item.Click += delegate(object o, EventArgs ea)
                    {
                        _history.GoBack(menuStrip, (int)((ToolStripMenuItem)o).Tag);
                    };

                    btnViewBack.DropDown.Items.Add(item);
                    i++;
                }
            }
        }

        private void btnViewForward_DropDownOpening(object sender, EventArgs e)
        {
            if (btnViewForward.Enabled)
            {
                btnViewForward.DropDown.Items.Clear();

                var i = 1;
                foreach (var s in _history.ForwardSteps)
                {
                    var item = new ToolStripMenuItem((s.Tag as IAssemblyTreeNode<TreeNode>).Title)
                    {
                        Tag = i,
                    };

                    item.Click += delegate(object o, EventArgs ea)
                    {
                        _history.GoForward(menuStrip, (int)((ToolStripMenuItem)o).Tag);
                    };

                    btnViewForward.DropDown.Items.Add(item);
                    i++;
                }
            }
        }

        private void trvWorkspace_AfterSelect(object sender, TreeViewEventArgs e)
        {
            _history.GoTo(trvWorkspace, e.Node);
            UpdateUIBaseOnWorkspace();
        }

        private void workspace_Changed(object sender, WorkspaceChangedEventArgs e)
        {
            switch (e.Reason)
            {
                case WorkspaceChangedReason.Load:
                    LoadWorkspaceUnits();
                    break;

                case WorkspaceChangedReason.Saved:
                    foreach (TreeNode node in trvWorkspace.Nodes)
                    {
                        var tag = node.Tag as IAssemblyTreeNode<TreeNode>;
                        node.Text = tag.Title;
                    }
                    break;

                case WorkspaceChangedReason.UnitAdded:
                    var addedNode = _treeNodeBuilder.BuildUnitNode(e.Unit);
                    trvWorkspace.Nodes.Add(addedNode);
                    break;

                case WorkspaceChangedReason.UnitRemoved:
                    var rnode = GetWorkspaceUnitTreeNode(e.Unit);
                    if (rnode != null)
                        rnode.Remove();
                    break;

                default: break;
            }

            UpdateUIBaseOnWorkspace();
        }

        void history_Changed(object sender, HistoryChangedEventArgs<TreeNode> e)
        {
            if (_history.Current == null)
            {
                propertyGrid.SelectedObject = null;
            }
            else
            {
                var tag = _history.Current.Tag as IAssemblyTreeNode<TreeNode>;
                propertyGrid.SelectedObject = tag.PropertyObject;

                if (trvWorkspace.SelectedNode != _history.Current)
                    trvWorkspace.SelectedNode = _history.Current;
            }

            UpdateUIBaseOnHistory();
        }

        private void UpdateUIBaseOnWorkspace()
        {
            // Change window title
            var perfix = _workspace.IsChanged ? "* " : "";
            this.Text = string.Format("{0}{1} - {2}", perfix,
                _workspace.Title, ProgramEntry.GetApplicationTitle());

            // Menu and toolbar buttons
            btnFileSaveWorkspace.Enabled = mnuFileSaveWorkspace.Enabled
                = _workspace.IsNew || _workspace.IsChanged;

            mnuFileRemoveAssembly.Enabled = GetSelectedWorkspaceUnit() != null;
            mnuViewRefresh.Enabled = btnViewRefresh.Enabled
                = GetSelectedWorkspaceUnit(true) != null;
        }

        private void UpdateUIBaseOnHistory()
        {
            btnViewBack.Enabled = mnuViewBack.Enabled = _history.CanGoBack;
            btnViewForward.Enabled = mnuViewForward.Enabled = _history.CanGoForward;
        }

        private WorkspaceUnit GetSelectedWorkspaceUnit()
        {
            return GetSelectedWorkspaceUnit(false);
        }

        private WorkspaceUnit GetSelectedWorkspaceUnit(bool recursively)
        {
            if (trvWorkspace.SelectedNode == null)
                return null;

            var node = trvWorkspace.SelectedNode;

            if (recursively)
            {
                while (node.Parent != null)
                    node = node.Parent;
            }

            var tag = node.Tag as IWorkspaceUnitNodeTag;
            return tag == null ? null : tag.WorkspaceUnit;
        }

        private TreeNode GetWorkspaceUnitTreeNode(WorkspaceUnit unit)
        {
            foreach (TreeNode node in trvWorkspace.Nodes)
            {
                var tag = node.Tag as IWorkspaceUnitNodeTag;
                if (tag != null && tag.WorkspaceUnit == unit)
                    return node;
            }

            return null;
        }

        /// <summary>
        /// Open a unit file.
        /// </summary>
        private void OpenUnit(string location)
        {
            try
            {
                _workspace.AddUnit(location);
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    string.Format("Cannot open assembly file '{0}'. {1}", location, ex.Message),
                    "Open Assembly", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        /// <summary>
        /// Open an workspace file.
        /// </summary>
        /// <param name="path">
        /// Path of the workspace file, or null for create a new workspace.
        /// </param>
        private void OpenWorkspaceFile(string path)
        {
            try
            {
                trvWorkspace.Nodes.Clear();

                _workspace = new Workspace();
                _workspace.Changed += workspace_Changed;

                if (!string.IsNullOrEmpty(path))
                    _workspace.Load(path);
                else
                    UpdateUIBaseOnWorkspace();
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    string.Format("Cannot open workspace file '{0}'. {1}", path, ex.Message),
                    "Open Workspace", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void LoadWorkspaceUnits()
        {
            trvWorkspace.Nodes.Clear();
            _history.Clear(null);
            foreach (var unit in _workspace.Units)
            {
                var node = _treeNodeBuilder.BuildUnitNode(unit);
                trvWorkspace.Nodes.Add(node);
            }
        }

        /// <summary>
        /// Close a workspace safely.
        /// If the workspace has been changed, prompt user to save it.
        /// </summary>
        /// <returns>
        /// True if user save the changed workspace or the workspace hasn't been changed,
        /// or false if user cancels the saving process.
        /// </returns>
        private bool SafeCloseWorkspace()
        {
            if (!_workspace.IsChanged)
                return true;

            var r = MessageBox.Show("The workspace has been changed, save it?", "Save",
                MessageBoxButtons.YesNoCancel, MessageBoxIcon.Information);

            switch (r)
            {
                case DialogResult.Yes: return SaveWorkspace();
                case DialogResult.No: return true;
                default: return false;
            }
        }

        /// <summary>
        /// Save the workspace.
        /// If the workspace is newly created, prompt user to select the file path.
        /// </summary>
        /// <returns>
        /// True if user really saved the workspace, or false if user cancelled the saving process.
        /// </returns>
        private bool SaveWorkspace()
        {
            var path = _workspace.Path;

            try
            {
                if (path == null)
                {
                    using (var dlg = new SaveFileDialog())
                    {
                        dlg.Filter = "Reflector .NET Workspace (*.reflector)|*.reflector|All Files (*.*)|*.*";
                        if (DialogResult.OK != dlg.ShowDialog())
                            return false;

                        path = dlg.FileName;
                    }
                }

                _workspace.Save(path);
            }
            catch (Exception ex)
            {
                MessageBox.Show(
                    string.Format("Cannot save workspace file '{0}'. {1}", path, ex.Message),
                    "Save Workspace", MessageBoxButtons.OK, MessageBoxIcon.Warning);

                return false;
            }

            return true;
        }
    }
}
